package com.fujimana.rockincat.restree.bytecode;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

public final class ConstPool {
    LongVector items;
    int       numOfItems;
    @SuppressWarnings("unchecked")
	HashMap classes;
    @SuppressWarnings("unchecked")
	HashMap strings;
    int       thisClassInfo;

    public static final int CONST_Class = ClassInfo.tag;

    public static final int CONST_Fieldref = FieldrefInfo.tag;

    public static final int CONST_Methodref = MethodrefInfo.tag;

    public static final int CONST_InterfaceMethodref
                                        = InterfaceMethodrefInfo.tag;
    public static final int CONST_String = StringInfo.tag;

    public static final int CONST_Integer = IntegerInfo.tag;

    public static final int CONST_Float = FloatInfo.tag;

    public static final int CONST_Long = LongInfo.tag;

    public static final int CONST_Double = DoubleInfo.tag;

    public static final int CONST_NameAndType = NameAndTypeInfo.tag;

    public static final int CONST_Utf8 = Utf8Info.tag;

    
    @SuppressWarnings("unchecked")
	public ConstPool(DataInputStream in) throws IOException {
        classes = new HashMap();
        strings = new HashMap();
        thisClassInfo = 0;
        /* read() initializes items and numOfItems, and do addItem(null).
         */
        read(in);
    }
    
    private int addItem(ConstInfo info) {
        items.addElement(info);
        return numOfItems++;
    }
    
    private void read(DataInputStream in) throws IOException {
        int n = in.readUnsignedShort();

        items = new LongVector(n);
        numOfItems = 0;
        addItem(null);          // index 0 is reserved by the JVM.

        while (--n > 0) {       // index 0 is reserved by JVM
            int tag = readOne(in);
            if ((tag == LongInfo.tag) || (tag == DoubleInfo.tag)) {
                addItem(new ConstInfoPadding());
                --n;
            }
        }

        int i = 1;
        while (true) {
            ConstInfo info = (ConstInfo)items.elementAt(i++);
            if (info == null)
                break;
            else
                info.makeHashtable(this);
        }
    }
    
    void setThisClassInfo(int i) {
        thisClassInfo = i;
    }
    
    @SuppressWarnings("unchecked")
	private int readOne(DataInputStream in) throws IOException {
        ConstInfo info;
        int tag = in.readUnsignedByte();
        switch (tag) {
        case Utf8Info.tag :                     // 1
            info = new Utf8Info(in, numOfItems);
            strings.put(((Utf8Info)info).string, info);
            break;
        case IntegerInfo.tag :                  // 3
            info = new IntegerInfo(in);
            break;
        case FloatInfo.tag :                    // 4
            info = new FloatInfo(in);
            break;
        case LongInfo.tag :                     // 5
            info = new LongInfo(in);
            break;
        case DoubleInfo.tag :                   // 6
            info = new DoubleInfo(in);
            break;
        case ClassInfo.tag :                    // 7
            info = new ClassInfo(in, numOfItems);
            // classes.put(<classname>, info);
            break;
        case StringInfo.tag :                   // 8
            info = new StringInfo(in);
            break;
        case FieldrefInfo.tag :                 // 9
            info = new FieldrefInfo(in);
            break;
        case MethodrefInfo.tag :                // 10
            info = new MethodrefInfo(in);
            break;
        case InterfaceMethodrefInfo.tag :       // 11
            info = new InterfaceMethodrefInfo(in);
            break;
        case NameAndTypeInfo.tag :              // 12
            info = new NameAndTypeInfo(in);
            break;
        default :
            throw new IOException("invalid constant type: " + tag);
        }

        addItem(info);
        return tag;
    }
    
    public String getUtf8Info(int index) {
        Utf8Info utf = (Utf8Info)getItem(index);
        return utf.string;
    }
    
    ConstInfo getItem(int n) {
        return (ConstInfo)items.elementAt(n);
    }
    
    int addConstInfoPadding() {
        return addItem(new ConstInfoPadding());
    }
    
    public String getClassName() {
        return getClassInfo(thisClassInfo).replaceAll("\\/", "\\.");
    }
    
    public String getClassInfo(int index) {
        ClassInfo c = (ClassInfo)getItem(index);
        if (c == null)
            return null;
        else
            return getUtf8Info(c.name);
    }
}

class NameAndTypeInfo extends ConstInfo {
    static final int tag = 12;
    int memberName;
    int typeDescriptor;

    public NameAndTypeInfo(DataInputStream in) throws IOException {
        memberName = in.readUnsignedShort();
        typeDescriptor = in.readUnsignedShort();
    }

    public int getTag() { return tag; }

}

class InterfaceMethodrefInfo extends MemberrefInfo {
    static final int tag = 11;

    public InterfaceMethodrefInfo(DataInputStream in) throws IOException {
        super(in);
    }

    public int getTag() { return tag; }

    public String getTagName() { return "Interface"; }
}

class MethodrefInfo extends MemberrefInfo {
    static final int tag = 10;

    public MethodrefInfo(DataInputStream in) throws IOException {
        super(in);
    }

    public int getTag() { return tag; }

    public String getTagName() { return "Method"; }

}

class FieldrefInfo extends MemberrefInfo {
    static final int tag = 9;

    public FieldrefInfo(DataInputStream in) throws IOException {
        super(in);
    }

    public int getTag() { return tag; }

    public String getTagName() { return "Field"; }

}

class StringInfo extends ConstInfo {
    static final int tag = 8;
    int string;

    public StringInfo(DataInputStream in) throws IOException {
        string = in.readUnsignedShort();
    }

    public int getTag() { return tag; }

}

class ClassInfo extends ConstInfo {
    static final int tag = 7;
    int name;
    int index;

    public ClassInfo(int className, int i) {
        name = className;
        index = i;
    }

    public ClassInfo(DataInputStream in, int i) throws IOException {
        name = in.readUnsignedShort();
        index = i;
    }

    public int getTag() { return tag; }

    public String getClassName(ConstPool cp) {
        return cp.getUtf8Info(name);
    };
}

class DoubleInfo extends ConstInfo {
    static final int tag = 6;
    double value;

    public DoubleInfo(DataInputStream in) throws IOException {
        value = in.readDouble();
    }

    public int getTag() { return tag; }
}

class FloatInfo extends ConstInfo {
    static final int tag = 4;
    float value;

    public FloatInfo(DataInputStream in) throws IOException {
        value = in.readFloat();
    }

    public int getTag() { return tag; }

}

class LongInfo extends ConstInfo {
    static final int tag = 5;
    long value;

    public LongInfo(DataInputStream in) throws IOException {
        value = in.readLong();
    }

    public int getTag() { return tag; }
}

class IntegerInfo extends ConstInfo {
    static final int tag = 3;
    int value;

    public IntegerInfo(DataInputStream in) throws IOException {
        value = in.readInt();
    }

    public int getTag() { return tag; }
    
}

class Utf8Info extends ConstInfo {
    static final int tag = 1;
    String string;
    int index;

    public Utf8Info(DataInputStream in, int i) throws IOException {
        string = in.readUTF();
        index = i;
    }

    public int getTag() { return tag; }

}

abstract class MemberrefInfo extends ConstInfo {
    int classIndex;
    int nameAndTypeIndex;

    public MemberrefInfo(DataInputStream in) throws IOException {
        classIndex = in.readUnsignedShort();
        nameAndTypeIndex = in.readUnsignedShort();
    }

    public abstract String getTagName();
}

abstract class ConstInfo {
    public abstract int getTag();

    public String getClassName(ConstPool cp) { return null; }
    public void renameClass(ConstPool cp, String oldName, String newName) {}
    @SuppressWarnings("unchecked")
	public void renameClass(ConstPool cp, Map classnames) {}

    void makeHashtable(ConstPool cp) {}
    
}

class ConstInfoPadding extends ConstInfo {
    public int getTag() { return 0; }

    @SuppressWarnings("unchecked")
	public int copy(ConstPool src, ConstPool dest, Map map) {
        return dest.addConstInfoPadding();
    }

    public void write(DataOutputStream out) throws IOException {}

    public void print(PrintWriter out) {
        out.println("padding");
    }
}